/*-------------------------------------------------------------------------
 *
 * amvalidate.c
 *	  索引访问方法的 amvalidate 和
 *	  amadjustmembers 功能的支持例程。
 *
 * Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/index/amvalidate.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amvalidate.h"
#include "access/htup_details.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "parser/parse_coerce.h"
#include "utils/syscache.h"


/*
 * identify_opfamily_groups() 返回一个 OpFamilyOpFuncGroup 结构体的列表，
 * 每个结构体代表家庭的操作符和支持函数列表中存在的左类型/右类型组合。
 * 如果此数据类型组合存在 amopstrategy K，我们在 operatorset 中设置位 1 << K， 
 * 同样适用于支持函数。使用 uint64 字段，我们可以处理最多 63 个操作符和函数号， 
 * 这在可预见的未来已经足够。
 *
 * 给定的 CatCLists 预期代表从 AMOPSTRATEGY 和 AMPROCNUM 缓存中提取的 
 * 单个 opfamily，因此它们将按这些缓存的第二及第三缓存键，即数据类型，排序。
 */
List * identify_opfamily_groups(CatCList *fc_oprlist, CatCList *fc_proclist)
{
	List	   *fc_result = NIL;
	OpFamilyOpFuncGroup *fc_thisgroup;
	Form_pg_amop fc_oprform;
	Form_pg_amproc fc_procform;
	int			fc_io,
				fc_ip;

	/* 我们需要列表有序；在正常操作中应该是正确的 */
	if (!fc_oprlist->ordered || !fc_proclist->ordered)
		elog(ERROR, "cannot validate operator family without ordered data");

	/*
	 * 并发地推进列表。得益于排序，我们应该能接连看到给定数据类型对的 
	 * 所有操作符和函数。
	 */
	fc_thisgroup = NULL;
	fc_io = fc_ip = 0;
	if (fc_io < fc_oprlist->n_members)
	{
		fc_oprform = (Form_pg_amop) GETSTRUCT(&fc_oprlist->members[fc_io]->tuple);
		fc_io++;
	}
	else
		fc_oprform = NULL;
	if (fc_ip < fc_proclist->n_members)
	{
		fc_procform = (Form_pg_amproc) GETSTRUCT(&fc_proclist->members[fc_ip]->tuple);
		fc_ip++;
	}
	else
		fc_procform = NULL;

	while (fc_oprform || fc_procform)
	{
		if (fc_oprform && fc_thisgroup &&
			fc_oprform->amoplefttype == fc_thisgroup->lefttype &&
			fc_oprform->amoprighttype == fc_thisgroup->righttype)
		{
			/* 操作符属于当前组；包含它并推进 */

			/* 忽略不在支持范围内的策略编号 */
			if (fc_oprform->amopstrategy > 0 && fc_oprform->amopstrategy < 64)
				fc_thisgroup->operatorset |= ((uint64) 1) << fc_oprform->amopstrategy;

			if (fc_io < fc_oprlist->n_members)
			{
				fc_oprform = (Form_pg_amop) GETSTRUCT(&fc_oprlist->members[fc_io]->tuple);
				fc_io++;
			}
			else
				fc_oprform = NULL;
			continue;
		}

		if (fc_procform && fc_thisgroup &&
			fc_procform->amproclefttype == fc_thisgroup->lefttype &&
			fc_procform->amprocrighttype == fc_thisgroup->righttype)
		{
			/* 程序属于当前组；包含它并推进 */

			/* 忽略不在支持范围内的函数编号 */
			if (fc_procform->amprocnum > 0 && fc_procform->amprocnum < 64)
				fc_thisgroup->functionset |= ((uint64) 1) << fc_procform->amprocnum;

			if (fc_ip < fc_proclist->n_members)
			{
				fc_procform = (Form_pg_amproc) GETSTRUCT(&fc_proclist->members[fc_ip]->tuple);
				fc_ip++;
			}
			else
				fc_procform = NULL;
			continue;
		}

		/* 新组的时间 */
		fc_thisgroup = (OpFamilyOpFuncGroup *) palloc(sizeof(OpFamilyOpFuncGroup));
		if (fc_oprform &&
			(!fc_procform ||
			 (fc_oprform->amoplefttype < fc_procform->amproclefttype ||
			  (fc_oprform->amoplefttype == fc_procform->amproclefttype &&
			   fc_oprform->amoprighttype < fc_procform->amprocrighttype))))
		{
			fc_thisgroup->lefttype = fc_oprform->amoplefttype;
			fc_thisgroup->righttype = fc_oprform->amoprighttype;
		}
		else
		{
			fc_thisgroup->lefttype = fc_procform->amproclefttype;
			fc_thisgroup->righttype = fc_procform->amprocrighttype;
		}
		fc_thisgroup->operatorset = fc_thisgroup->functionset = 0;
		fc_result = lappend(fc_result, fc_thisgroup);
	}

	return fc_result;
}

/*
 * 验证 opclass 支持函数的签名（参数和结果类型）。如果正确则返回 true，
 * 否则返回 false。
 *
 * “...” 代表 maxargs 参数类型的 OID。如果 “exact” 为 true， 
 * 它们必须完全匹配函数参数类型，否则只需以二进制可强制转换的方式匹配。
 * 在任何情况下，函数结果类型必须与 restype 完全匹配。
 */
bool check_amproc_signature(Oid fc_funcid, Oid fc_restype, bool fc_exact,
					   int fc_minargs, int fc_maxargs,...)
{
	bool		fc_result = true;
	HeapTuple	fc_tp;
	Form_pg_proc fc_procform;
	va_list		fc_ap;
	int			fc_i;

	fc_tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for function %u", fc_funcid);
	fc_procform = (Form_pg_proc) GETSTRUCT(fc_tp);

	if (fc_procform->prorettype != fc_restype || fc_procform->proretset ||
		fc_procform->pronargs < fc_minargs || fc_procform->pronargs > fc_maxargs)
		fc_result = false;

	va_start(fc_ap, fc_maxargs);
	for (fc_i = 0; fc_i < fc_maxargs; fc_i++)
	{
		Oid			fc_argtype = va_arg(fc_ap, Oid);

		if (fc_i >= fc_procform->pronargs)
			continue;
		if (fc_exact ? (fc_argtype != fc_procform->proargtypes.values[fc_i]) :
			!IsBinaryCoercible(fc_argtype, fc_procform->proargtypes.values[fc_i]))
			fc_result = false;
	}
	va_end(fc_ap);

	ReleaseSysCache(fc_tp);
	return fc_result;
}

/*
 * 验证 opclass 选项支持函数的签名，应该是 'void(internal)'。
 */
bool check_amoptsproc_signature(Oid fc_funcid)
{
	return check_amproc_signature(fc_funcid, VOIDOID, true, 1, 1, INTERNALOID);
}

/*
 * 验证 opclass 操作符的签名（参数和结果类型）。如果正确则返回 true，
 * 否则返回 false。
 *
 * 当前，我们可以硬编码此签名，仅接受二元操作符。
 * 此外，我们可以坚持要求完全类型匹配，因为给定的左类型/右类型 
 * 来自 pg_amop，并且应该始终与操作符完全匹配。
 */
bool check_amop_signature(Oid fc_opno, Oid fc_restype, Oid fc_lefttype, Oid fc_righttype)
{
	bool		fc_result = true;
	HeapTuple	fc_tp;
	Form_pg_operator fc_opform;

	fc_tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_opno));
	if (!HeapTupleIsValid(fc_tp))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for operator %u", fc_opno);
	fc_opform = (Form_pg_operator) GETSTRUCT(fc_tp);

	if (fc_opform->oprresult != fc_restype || fc_opform->oprkind != 'b' ||
		fc_opform->oprleft != fc_lefttype || fc_opform->oprright != fc_righttype)
		fc_result = false;

	ReleaseSysCache(fc_tp);
	return fc_result;
}

/*
 * 获取属于 opfamily 的 opclass 的 OID，并接受指定类型作为输入类型。
 * 如果没有这样的 opclass，则返回 InvalidOid。
 *
 * 如果有超过一个这样的 opclass，您将获得其中的一个随机值。
 * 由于这不应发生，我们不会浪费周期进行检查。
 *
 * 我们可以从 opfamily 查找 AM 的 OID，但所有现有调用者都知道这个值 
 * 或者可以在没有额外查找的情况下获取它，因此我们让它们传递该值。
 */
Oid opclass_for_family_datatype(Oid fc_amoid, Oid fc_opfamilyoid, Oid fc_datatypeoid)
{
	Oid			fc_result = InvalidOid;
	CatCList   *fc_opclist;
	int			fc_i;

	/*
	 * 我们在所有 AM 的 opclasses 中搜索以查看是否有匹配。这有点低效，
	 * 但没有更好的索引可用。这也节省了明确检查 opfamily 是否属于 AM 的 
	 * 过程。
	 */
	fc_opclist = SearchSysCacheList1(CLAAMNAMENSP, ObjectIdGetDatum(fc_amoid));

	for (fc_i = 0; fc_i < fc_opclist->n_members; fc_i++)
	{
		HeapTuple	fc_classtup = &fc_opclist->members[fc_i]->tuple;
		Form_pg_opclass fc_classform = (Form_pg_opclass) GETSTRUCT(fc_classtup);

		if (fc_classform->opcfamily == fc_opfamilyoid &&
			fc_classform->opcintype == fc_datatypeoid)
		{
			fc_result = fc_classform->oid;
			break;
		}
	}

	ReleaseCatCacheList(fc_opclist);

	return fc_result;
}

/*
 * 该数据类型是否是 btree opfamily 的合法输入类型？
 */
bool opfamily_can_sort_type(Oid fc_opfamilyoid, Oid fc_datatypeoid)
{
	return OidIsValid(opclass_for_family_datatype(BTREE_AM_OID,
												  fc_opfamilyoid,
												  fc_datatypeoid));
}
